﻿using DocBot.Objects;
using DocBot.Util;
using DocBot.Util.Collections;
using DocBot.ViewModel;

using System;
using System.Collections.Generic;

using Vlc.DotNet.Core;
using Vlc.DotNet.Core.Medias;
using Vlc.DotNet.Wpf;

namespace DocBot.Bot
{
	public class MediaPlayer
	{
		internal bool playable { get; set; }
		private bool _excuteSongSkip { get; set; }
		private bool _buffering { get; set; }
		private VlcControl _vlcplayer { get; set; }
		private AudioDeviceType _type { get; set; }
		private String _nowplaying = String.Empty;
		private int _voteskipcounter { get; set; }

		private System.Timers.Timer _maxSongDurationTimer = new System.Timers.Timer();
		private System.Timers.Timer _skipPreventionTimer = new System.Timers.Timer();

		private static MainWindow _window;
		private static PlaylistViewModel _playlist;
		private static LogViewModel _log;
		private static ChatViewModel _chat;
		private static CustomCommandViewModel _customcommands;
		private static BlacklistViewModel _blacklist;

		#region Constructor
		public MediaPlayer(MainWindow mainwindow, AudioDeviceType type)
		{
			playable = false;
			_window = mainwindow;
			_playlist = (PlaylistViewModel)_window.Playlist.DataContext;
			_log = (LogViewModel)_window.FindResource("LogModel");
			_chat = (ChatViewModel)_window.Chat.DataContext;
			_customcommands = (CustomCommandViewModel)_window.CustomCommands.DataContext;
			_blacklist = (BlacklistViewModel)_window.Blacklist.DataContext;

			_type = type;

			if (!Settings.Instance.IsDefaultConfig)
				this.initializeVLC(type);
			if (_vlcplayer == null) return;
			playable = true;
		}
		#endregion

		#region Init
		/// <summary>
		/// initializes the vlc player with parameters, initializes events
		/// </summary>
		private void initializeVLC(AudioDeviceType type)
		{
			//init
			VlcContext.LibVlcDllsPath = Settings.Instance.VLC_LibVlcDllPath;
			VlcContext.LibVlcPluginsPath = Settings.Instance.VLC_LibVlcPluginPath;

			Message _ErrorDeviceListMessage = new Message("Devicelist empty, Mediaplayer not initialized", MessageType.Log);
			String _audioDevice = String.Empty;

			// Get default Audiodevice if Settings not filled or kill if nothing is working
			switch (_type)
			{
				case AudioDeviceType.FollowerAlert:
					#region AudioDeviceType.FollowerAlert
					FollowerAudioDeviceViewModel _FollowerAudioDevices = new FollowerAudioDeviceViewModel();
					if (_FollowerAudioDevices == null)
					{
						Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
						return;
					}

					if (Settings.Instance.VLC_MediaPlayerWaveOutDevice == String.Empty)
					{
						if (_FollowerAudioDevices.AudioDevices.Count > -1)
							if (_FollowerAudioDevices.SelectedAudioDevice == -1)
								_audioDevice = _FollowerAudioDevices.AudioDevices[0].Name;
							else
								_audioDevice = _FollowerAudioDevices.AudioDevices[_FollowerAudioDevices.SelectedAudioDevice].ToString();
						else
						{
							Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
							return;
						}
					}
					else
						_audioDevice = Settings.Instance.VLC_FollowerWaveOutDevice;
					break;
					#endregion
				case AudioDeviceType.MediaPlayer:
					#region AudioDeviceType.MediaPlayer
					MediaPlayerAudioDeviceViewModel _MediaPlayerAudioDevices = new MediaPlayerAudioDeviceViewModel();
					if (_MediaPlayerAudioDevices == null)
					{
						Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
						return;
					}

					if (Settings.Instance.VLC_MediaPlayerWaveOutDevice == String.Empty)
					{
						if (_MediaPlayerAudioDevices.AudioDevices.Count > -1)
							if (_MediaPlayerAudioDevices.SelectedAudioDevice == -1)
								_audioDevice = _MediaPlayerAudioDevices.AudioDevices[0].Name;
							else
								_audioDevice = _MediaPlayerAudioDevices.AudioDevices[_MediaPlayerAudioDevices.SelectedAudioDevice].ToString();
						else
						{
							Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
							return;
						}
					}
					else
						_audioDevice = Settings.Instance.VLC_MediaPlayerWaveOutDevice;
					break;
					#endregion
				case AudioDeviceType.Soundboard:
					#region AudioDeviceType.Soundboard
					SoundboardAudioDeviceViewModel _SoundBoardAudioDevices = new SoundboardAudioDeviceViewModel();
					if (_SoundBoardAudioDevices == null)
					{
						Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
						return;
					}

					if (Settings.Instance.VLC_MediaPlayerWaveOutDevice == String.Empty)
					{
						if (_SoundBoardAudioDevices.AudioDevices.Count > -1)
							if (_SoundBoardAudioDevices.SelectedAudioDevice == -1)
								_audioDevice = _SoundBoardAudioDevices.AudioDevices[0].Name;
							else
								_audioDevice = _SoundBoardAudioDevices.AudioDevices[_SoundBoardAudioDevices.SelectedAudioDevice].ToString();
						else
						{
							Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
							return;
						}
					}
					else
						_audioDevice = Settings.Instance.VLC_SoundboardWaveOutDevice;
					break;
					#endregion
				case AudioDeviceType.SubscriberAlert:
					#region AudioDeviceType.Soundboard
					SubscriberAudioDeviceViewModel _SubscriberAudioDevices = new SubscriberAudioDeviceViewModel();
					if (_SubscriberAudioDevices == null)
					{
						Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
						return;
					}

					if (Settings.Instance.VLC_MediaPlayerWaveOutDevice == String.Empty)
					{
						if (_SubscriberAudioDevices.AudioDevices.Count > -1)
							if (_SubscriberAudioDevices.SelectedAudioDevice == -1)
								_audioDevice = _SubscriberAudioDevices.AudioDevices[0].Name;
							else
								_audioDevice = _SubscriberAudioDevices.AudioDevices[_SubscriberAudioDevices.SelectedAudioDevice].ToString();
						else
						{
							Messenger.Instance.Messages.Enqueue(_ErrorDeviceListMessage);
							return;
						}
					}
					else
						_audioDevice = Settings.Instance.VLC_SubScriberWaveOutDevice;
					break;
					#endregion
				default:
					throw new NotImplementedException();
			}


			#region config settings
			///settings
			//Ignore the VLC configuration file
			//VlcContext.StartupOptions.IgnoreConfig = true;

			//log settings
			//VlcContext.StartupOptions.LogOptions.LogInFile = true;
			//VlcContext.StartupOptions.LogOptions.ShowLoggerConsole = true;
			//VlcContext.StartupOptions.LogOptions.Verbosity = VlcLogVerbosities.Debug;
			#endregion

			//audio settings
			VlcContext.StartupOptions.AddOption("--aout=waveout");
			VlcContext.StartupOptions.AddOption("--waveout-audio-device=" + _audioDevice);
			VlcContext.StartupOptions.AddOption("--ffmpeg-hw");
			VlcContext.StartupOptions.AddOption("--no-video");

			try
			{
				_vlcplayer = new VlcControl();
			}
			catch (System.IO.FileNotFoundException e)
			{
				Messenger.Instance.Messages.Enqueue(new Message("initializeVLC: " + e.Message + " " + e.FileName, MessageType.Log));
			}
			catch (System.IO.DirectoryNotFoundException f)
			{
				Messenger.Instance.Messages.Enqueue(new Message("initializeVLC: " + f.Message + " " + f.Source, MessageType.Log));
			}
			catch (Exception g)
			{
				Messenger.Instance.Messages.Enqueue(new Message("initializeVLC: " + g.Message + " " + g.Source, MessageType.Log));
			}
			if (_vlcplayer == null) return;
			///events
			_vlcplayer.Playing += _vlcplayer_Playing;
			_vlcplayer.EndReached += _vlcplayer_EndReached;
			_vlcplayer.EncounteredError += _vlcplayer_EncounteredError;

			// setup Timer
			_maxSongDurationTimer.Interval = Settings.Instance.Media_MaxSongDuration * 1000;
			_skipPreventionTimer.Interval = Settings.Instance.Media_AutoSkipThreshold * 1000;

			// add Timer events
			_maxSongDurationTimer.Elapsed += _maxSongDurationTimer_Elapsed;
			_skipPreventionTimer.Elapsed += _skipPreventionTimer_Elapsed;

			switch (_type)
			{
				case AudioDeviceType.FollowerAlert:
					#region AudioDeviceType.FollowerAlert
					Messenger.Instance.Messages.Enqueue(new Message("FollowerAlert initialized", MessageType.Log));
					break;
					#endregion
				case AudioDeviceType.MediaPlayer:
					#region AudioDeviceType.MediaPlayer
					Messenger.Instance.Messages.Enqueue(new Message("Mediaplayer initialized", MessageType.Log));
					break;
					#endregion
				case AudioDeviceType.Soundboard:
					#region AudioDeviceType.Soundboard
					Messenger.Instance.Messages.Enqueue(new Message("Soundboard initialized", MessageType.Log));
					break;
					#endregion
				case AudioDeviceType.SubscriberAlert:
					#region AudioDeviceType.Soundboard
					Messenger.Instance.Messages.Enqueue(new Message("SubscriberAlert initialized", MessageType.Log));
					break;
					#endregion
				default:
					throw new NotImplementedException();
			}
			// voteskip
			_voteskipcounter = 0;
		}

		#endregion

		#region Playerinteraction
		/// <summary>
		/// if no song is selected, then the first song in the playlist will be selected
		/// </summary>
		private void setDefaultSong()
		{
			if (_playlist.SelectedPlayListItem == -1)
				if (_playlist.Songs.Count > 0)
					_playlist.SelectedPlayListItem = 0;
		}

		public void play(String url)
		{
			if (this.playable)
			{
				_vlcplayer.Stop();
				_vlcplayer.Medias.RemoveAt(0);
				_vlcplayer.Media = null;

				MediaBase nm = new LocationMedia(url);
				_vlcplayer.Play(nm);
			}
		}

		/// <summary>
		/// plays the currently selected song, defaults to the first song if none is selected, does nothing if the playlist is empty
		/// </summary>
		public void play()
		{
			setDefaultSong();
			int i = _playlist.SelectedPlayListItem;

			if (this.playable)
				if (i > -1)
				{
					_playlist.Songs[i].PlayCount++;
					this.play(_playlist.Songs[i].Url);
				}
		}

		/// <summary>
		/// stops playback
		/// </summary>
		public void Stop()
		{
			if (this.playable)
				_vlcplayer.Stop();
		}

		public void Previous()
		{
			setDefaultSong();
			int i = _playlist.SelectedPlayListItem;

			if (this.playable)
			{
				switch (Settings.Instance.VLC_PlayBackType)
				{
					case Playmode.PlayAll: selectByPreviousSongOptionPlayAll(i);
						break;
					case Playmode.RepeatAll: selectByPreviousSongOptionRepeatAll(i);
						break;
					case Playmode.Random: Random();
						break;
					case Playmode.RandomRepeatAll: selectBySongOptionRandomRepeat();
						break;
					case Playmode.RepeatSingle:
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
						break;
				}
			}
		}

		public void Next()
		{
			setDefaultSong();
			int i = _playlist.SelectedPlayListItem;

			if (this.playable)
			{
				switch (Settings.Instance.VLC_PlayBackType)
				{
					case Playmode.PlayAll: selectByNextSongOptionPlayAll(i);
						break;
					case Playmode.RepeatAll: selectByNextSongOptionRepeatAll(i);
						break;
					case Playmode.Random: Random();
						break;
					case Playmode.RandomRepeatAll: selectBySongOptionRandomRepeat();
						break;
					case Playmode.RepeatSingle:
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
						break;
				}
			}
		}

		public void Skip(bool vote)
		{
			if (vote)
			{
				_voteskipcounter++;
				if (_voteskipcounter >= Settings.Instance.Media_MaxVoteCounter)
					Next();
			}
			else
				if (_excuteSongSkip)
					Next();
		}

		#region SongSelection

		#region SelectPreviousSong
		private void selectByPreviousSongOptionPlayAll(int i)
		{
			i--;
			if (this.playable)
				if (i > -1)
				{
					if (i <= _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
					else
						Messenger.Instance.Messages.Enqueue(new Message("End of PlayList reached. No previous Song available", MessageType.Log));
				}
		}

		private void selectByPreviousSongOptionRepeatAll(int i)
		{
			i--;
			if (this.playable)
				if (i > -1)
				{
					if (i <= _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
					else
					{
						i = 0;
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
				}
		}
		#endregion

		#region SelectNextSong
		private void selectByNextSongOptionPlayAll(int i)
		{
			i++;
			if (this.playable)
				if (i > -1)
				{
					if (i <= _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
					else
						Messenger.Instance.Messages.Enqueue(new Message("End of PlayList reached. No next Song available", MessageType.Log));
				}
		}

		private void selectByNextSongOptionRepeatAll(int i)
		{
			i++;
			if (this.playable)
				if (i > -1)
				{
					if (i < _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
					else
					{
						i = 0;
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
				}
		}

		#endregion

		#region Select Random

		private void selectBySongOptionRandomRepeat()
		{
			int i = selectRandomByPlays();
			if (this.playable)
				if (i > -1)
				{
					if (i <= _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						_playlist.SelectedPlayListItem = i;
						this.play(_playlist.Songs[i].Url);
					}
					else
						Messenger.Instance.Messages.Enqueue(new Message("Selection of Random Playlistitem failed.(Out of bounds)", MessageType.Log));
				}
		}

		private int selectRandomByPlays()
		{
			int min = int.MaxValue;
			// get the number of minimum plays
			foreach (PlaylistItem p in _playlist.Songs)
			{
				if (min > p.PlayCount)
					min = p.PlayCount;
			}

			// get all songs played the least
			List<PlaylistItem> list = new List<PlaylistItem>();
			foreach (PlaylistItem p in _playlist.Songs)
			{
				if (min == p.PlayCount)
					list.Add(p);
			}
			System.Random r = new System.Random();

			// pick a random song out of the least played songs and return its index
			return _playlist.Songs.IndexOf(list[r.Next(list.Count)]);
		}

		private int selectRandom()
		{
			System.Random r = new System.Random();

			return r.Next(_playlist.Songs.Count);
		}

		public void Random()
		{
			System.Random r = new System.Random();

			int i = selectRandom();

			if (this.playable)
				if (i > -1)
				{
					if (i <= _playlist.Songs.Count)
					{
						_playlist.Songs[i].PlayCount++;
						play(_playlist.Songs[i].Url);
						_playlist.SelectedPlayListItem = i;
					}
					else
						Messenger.Instance.Messages.Enqueue(new Message("End of PlayList reached. No next Song available", MessageType.Log));
				}
		}

		#endregion

		#endregion

		public void setVolume(double NewValue)
		{
			_vlcplayer.AudioProperties.Volume = Convert.ToInt32(NewValue);
		}

		public void setSilent(bool par)
		{
			_vlcplayer.AudioProperties.IsMute = !par;
		}
		#endregion

		#region TimerEvents

		void _skipPreventionTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			if (Settings.Instance.DebugMode)
				Messenger.Instance.Messages.Enqueue(new Message("skip available now", MessageType.Log));
			_skipPreventionTimer.Stop();
			_excuteSongSkip = true;
		}

		void _maxSongDurationTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			if (Settings.Instance.DebugMode)
				Messenger.Instance.Messages.Enqueue(new Message("Playing next Song. Requested song was too long.", MessageType.Log));
			_maxSongDurationTimer.Stop();
			Next();
		}
		#endregion

		#region PlayEvents
		void _vlcplayer_EndReached(VlcControl sender, VlcEventArgs<EventArgs> e)
		{
			_voteskipcounter = 0;
			if (!_buffering)
			{
				Messenger.Instance.Messages.Enqueue(new Message("End reached: " + _nowplaying, MessageType.Log));
				_nowplaying = String.Empty;
				UpdateSettings();
				Next();
			}
		}

		/// <summary>
		/// fires when a track starts playing (also includes the buffering of vlcplayer when fetching videodata from youtube)
		/// </summary>
		private void _vlcplayer_Playing(VlcControl sender, VlcEventArgs<EventArgs> e)
		{
			// the vlc control seems to always buffer a track before playing it. 
			//I didn't find the original track url in the sender object so i added a switch, which sends the url to my log while buffering it
			if (_buffering)
			{
				// Playback actually started
				_maxSongDurationTimer.Start();
				_skipPreventionTimer.Start();
				_excuteSongSkip = false;
				_buffering = false;
			}
			else
			{
				UpdateSettings();
				// _player is now buffering the song
				_nowplaying = sender.Media.MRL;
				Messenger.Instance.Messages.Enqueue(new Message("now playing: " + sender.Media.MRL, MessageType.Log));
				_buffering = true;
			}
		}

		/// <summary>
		/// gets fired when the media player encounters an error; checks if the player is in an error state and skips the current track to continue playback
		/// </summary>
		void _vlcplayer_EncounteredError(VlcControl sender, VlcEventArgs<EventArgs> e)
		{
			if (_vlcplayer.State.Equals(Vlc.DotNet.Core.Interops.Signatures.LibVlc.Media.States.Error))
			{
				if (Settings.Instance.DebugMode)
					Messenger.Instance.Messages.Enqueue(new Message("EncounteredError event fired", MessageType.Log));
				Messenger.Instance.Messages.Enqueue(new Message("Skip because broken: " + _vlcplayer.Media.Metadatas.Title, MessageType.LogAndChat));

				_playlist.RemoveItemMethod();
				Next();
			}
		}
		#endregion

		#region Util Methods
		private void UpdateSettings()
		{
			_maxSongDurationTimer.Interval = Settings.Instance.Media_MaxSongDuration * 1000;
			_skipPreventionTimer.Interval = Settings.Instance.Media_AutoSkipThreshold * 1000;

			switch (_type)
			{
				case AudioDeviceType.FollowerAlert:
					_vlcplayer.AudioProperties.IsMute = !Settings.Instance.VLC_FollowerSilent;
					_vlcplayer.AudioProperties.Volume = Convert.ToInt32(Settings.Instance.VLC_FollowerVolume);
					break;
				case AudioDeviceType.MediaPlayer:
					_vlcplayer.AudioProperties.IsMute = !Settings.Instance.VLC_MediaPlayerSilent;
					_vlcplayer.AudioProperties.Volume = Convert.ToInt32(Settings.Instance.VLC_MediaPlayerVolume);
					break;
				case AudioDeviceType.Soundboard:
					_vlcplayer.AudioProperties.IsMute = !Settings.Instance.VLC_SoundboardSilent;
					_vlcplayer.AudioProperties.Volume = Convert.ToInt32(Settings.Instance.VLC_SoundboardVolume);
					break;
				case AudioDeviceType.SubscriberAlert:
					_vlcplayer.AudioProperties.IsMute = !Settings.Instance.VLC_SubScriberSilent;
					_vlcplayer.AudioProperties.Volume = Convert.ToInt32(Settings.Instance.VLC_SubScriberVolume);
					break;
				default:
					throw new NotImplementedException();
			}
		}
		#endregion

	}
}